/**
 * Tablero.java
 *
 * Fecha de creación: May 5, 2012,3:02:06 PM
 *
 * Autor: José Ignacio Rivero Dávila
 */
package model;

import java.util.LinkedList;
import java.util.Random;
import util.FileReaderUtil;
import util.*;

public class Board extends AbstractModel implements Comparable<Board> {

    private static final Random rnd = new Random();
    private Checker[][] positions = new Checker[8][8];
    public final static Checker[][] ESTADO_INICIAL = {{Checker.EMPTY, Checker.RED, Checker.EMPTY, Checker.RED, Checker.EMPTY, Checker.RED, Checker.EMPTY, Checker.RED},
        {Checker.RED, Checker.EMPTY, Checker.RED, Checker.EMPTY, Checker.RED, Checker.EMPTY, Checker.RED, Checker.EMPTY},
        {Checker.EMPTY, Checker.RED, Checker.EMPTY, Checker.RED, Checker.EMPTY, Checker.RED, Checker.EMPTY, Checker.RED},
        {Checker.EMPTY, Checker.EMPTY, Checker.EMPTY, Checker.EMPTY, Checker.EMPTY, Checker.EMPTY, Checker.EMPTY, Checker.EMPTY},
        {Checker.EMPTY, Checker.EMPTY, Checker.EMPTY, Checker.EMPTY, Checker.EMPTY, Checker.EMPTY, Checker.EMPTY, Checker.EMPTY},
        {Checker.BLACK, Checker.EMPTY, Checker.BLACK, Checker.EMPTY, Checker.BLACK, Checker.EMPTY, Checker.BLACK, Checker.EMPTY},
        {Checker.EMPTY, Checker.BLACK, Checker.EMPTY, Checker.BLACK, Checker.EMPTY, Checker.BLACK, Checker.EMPTY, Checker.BLACK},
        {Checker.BLACK, Checker.EMPTY, Checker.BLACK, Checker.EMPTY, Checker.BLACK, Checker.EMPTY, Checker.BLACK, Checker.EMPTY}
    };
    private byte blackCheckers;
    private byte redCheckers;
    private byte redKings;
    private byte blackKings;
    private int heuristic;
    private Movement lastMove;
    private Movement nextMove;
    private Position lastEaten;
    private Checker gameWonByPlayer;

    public Board() {
        positions = ESTADO_INICIAL;
        gameWonByPlayer=null;
        countCheckers();
        firePropertyChange("positions", null, positions);
    }

    public Board(Checker[][] posiciones) {
        this.positions = posiciones;
        gameWonByPlayer=null;
        countCheckers();
    }

    public Board(Checker[][] posiciones, byte blackCheckers, byte redCheckers, byte blackKings, byte redKings) {
        this.positions = posiciones;
        this.blackCheckers = blackCheckers;
        this.redCheckers = redCheckers;
        this.blackKings = blackKings;
        this.redKings = redKings;
        gameWonByPlayer=null;
    }

    public Checker[][] getPositions() {
        return positions;
    }

    public void setPosiciones(Checker[][] posiciones) {
        this.positions = posiciones;
    }

    public byte getBlackCheckers() {
        return blackCheckers;
    }

    public void setBlackCheckers(byte blackCheckers) {
        this.blackCheckers = blackCheckers;
    }

    public byte getBlackKings() {
        return blackKings;
    }

    public void setBlackKings(byte blackKings) {
        this.blackKings = blackKings;
    }

    public byte getRedCheckers() {
        return redCheckers;
    }

    public void setRedCheckers(byte redCheckers) {
        this.redCheckers = redCheckers;
    }

    public byte getRedKings() {
        return redKings;
    }

    public void setRedKings(byte redKings) {
        this.redKings = redKings;
    }

    public int getHeuristic(boolean color) {
        if (heuristic == 0) {
            calculateHeuristic(color);
        }
        return heuristic;
    }

    public void setGameWonByPlayer(Checker gameWonByPlayer) {
        this.gameWonByPlayer = gameWonByPlayer;
        firePropertyChange("gameWonByPlayer", null, gameWonByPlayer);
    }
    

    private void countCheckers() {
        blackCheckers = 0;
        redCheckers = 0;
        for (int i = 0; i < positions.length; i++) {
            for (int j = 0; j < positions[0].length; j++) {
                if (positions[i][j] == Checker.RED || positions[i][j] == Checker.REDKING) {
                    redCheckers++;

                    if (positions[i][j] == Checker.REDKING) {
                        redKings++;
                    }
                } else if (positions[i][j] == Checker.BLACK || positions[i][j] == Checker.BLACKKING) {
                    blackCheckers++;
                    if (positions[i][j] == Checker.BLACKKING) {
                        blackKings++;
                    }

                }
            }
        }
    }

    public void setNextMove(Movement nextMove) {
        this.nextMove = nextMove;
    }

    public Movement getLastMove() {
        return lastMove;
    }

    public Movement getNextMove() {
        return nextMove;
    }

    //falta hacer que si el juego ya está "ganado" entonces que heche una excepcion
    //diciendo quien es el ganador. Tiene que guardar el color del ganador en 
    //playerThatWon o ponerle true a la variable "tie" 
    //todo esto lo tiene que hacer después de realizar la movida
    //(Si se intenta hacer una movida depués de que el juego ya está ganado,
    //debe echar una MovementNotAllowedException)
    public void move(Movement mv) throws MovementNotAllowedException{
        Checker[][] oldPos = new Checker[8][8];
        for (int i = 0; i < oldPos.length; i++) {
            for (int j = 0; j < oldPos.length; j++) {
                oldPos[i][j] = positions[i][j];
            }
        }
        //Position[] pos = mv.getListOfPositions().toArray(new Position[0]);
        Position iniPos;
        Position finalPos;
        Checker checkerType;
        iniPos = mv.getListOfPositions().getFirst();
        checkerType = positions[iniPos.i][iniPos.j];
        boolean isBlack = checkerType.equals(Checker.BLACK) || checkerType.equals(Checker.BLACKKING);
        
        MovementNotAllowedException exc = isValidMovement(mv);
        
        
        if (exc != null) {
            throw exc;
        }
        if (!mv.isJump()) {
            iniPos = mv.getListOfPositions().getFirst();
            finalPos = mv.getListOfPositions().getLast();
            positions[finalPos.i][finalPos.j] = positions[iniPos.i][iniPos.j];
            positions[iniPos.i][iniPos.j] = Checker.EMPTY;
        } else {

            iniPos = mv.getListOfPositions().getFirst();
            Position[] arrPos = mv.getListOfPositions().toArray(new Position[0]);
            checkerType = positions[iniPos.i][iniPos.j];

            for (int i = 1; i < arrPos.length; i++) {
                finalPos = arrPos[i];
                positions[iniPos.i][iniPos.j] = Checker.EMPTY;
                eatPosition(new Position((iniPos.i + finalPos.i) / 2, (iniPos.j + finalPos.j) / 2));
                positions[finalPos.i][finalPos.j] = checkerType;
                iniPos = arrPos[i];
            }
        }
        finalPos = mv.getListOfPositions().getLast();
        checkerType = positions[finalPos.i][finalPos.j];
        if (checkerType == Checker.BLACK && finalPos.i == 0) {
            positions[finalPos.i][finalPos.j] = Checker.BLACKKING;
        } else if (checkerType == Checker.RED && finalPos.i == 7) {
            positions[finalPos.i][finalPos.j] = Checker.REDKING;
        }
        lastMove = mv;
        firePropertyChange("positions", oldPos, positions);
        countCheckers();
        playerHasWon(isBlack);
    }

    private void eatPosition(Position pos) {
        positions[pos.i][pos.j] = Checker.EMPTY;
        firePropertyChange("lastEaten", lastEaten, pos);
        lastEaten = pos;
    }

    private MovementNotAllowedException isValidMovement(Movement mv) {
        boolean isValid = true;
        String msg = "Sin error";
        if (mv.getListOfPositions().size() < 2) {
            isValid = false;
            msg = "El movimiento sólo tiene una posición.";
        }
        Position ini=mv.getListOfPositions().getFirst();
        if(this.positions[ini.i][ini.j].equals(Checker.EMPTY)){
            isValid=false;
            msg="La posicion inicial está vacía.";
        }
        else if (!mv.isJump()) {
            if (mv.getListOfPositions().size() != 2) {
                isValid = false;
                msg = "No es jump y tiene más de dos posiciones";
            } else {
                if (isInvalidMov(mv)) {
                    isValid = false;
                    msg = "El jugador debe saltar";
                }
                Position iniPos = mv.getListOfPositions().getFirst();
                Position finalPos = mv.getListOfPositions().getLast();
                if (finalPos.i > 7 || finalPos.i < 0 || finalPos.j > 7 || finalPos.j < 0) {
                    isValid = false;
                    msg = "Posicion fuera del tablero";
                }
                int diffPosI = iniPos.i - finalPos.i;
                int diffPosJ = iniPos.j - finalPos.j;
                if (Math.abs(diffPosI) != 1 || Math.abs(diffPosJ) != 1) {
                    isValid = false;
                    msg = "Jugada inválida.";
                } else if (positions[finalPos.i][finalPos.j] != Checker.EMPTY) {
                    isValid = false;
                    msg = "El destino no esta vacio";
                } else if (positions[iniPos.i][iniPos.j] == Checker.BLACK) {
                    if (finalPos.i >= iniPos.i) {
                        isValid = false;
                        msg = "Es negra y no avanza en el sentido correcto";
                    }
                } else if (positions[iniPos.i][iniPos.j] == Checker.RED) {
                    if (finalPos.i <= iniPos.i) {
                        isValid = false;
                        msg = "Es roja y no avanza en el sentido correcto";
                    }
                }
            }
        } else {
            Position[] arrPos = mv.getListOfPositions().toArray(new Position[0]);
            Position iniPos = arrPos[0];
            Position finalPos;
            int diffPosI;
            int diffPosJ;
            if (isInvalidMov(mv)) {
                isValid = false;
                msg = "El jugador puede hacer más saltos subsecuentes.";
            }
            //Auxiliary board to simulate jumps
            Board aux = clone();
            Checker[][] auxPos = aux.getPositions();
            Checker checkerType = positions[iniPos.i][iniPos.j];
            for (int i = 1; i < arrPos.length; i++) {
                finalPos = arrPos[i];
                if (finalPos.i > 7 || finalPos.i < 0 || finalPos.j > 7 || finalPos.j < 0) {
                    isValid = false;
                    msg = "Posicion final fuera del tablero";
                }
                diffPosI = iniPos.i - finalPos.i;
                diffPosJ = iniPos.j - finalPos.j;
                if (Math.abs(diffPosI) != 2 || Math.abs(diffPosJ) != 2) {
                    isValid = false;
                    System.out.println(mv.toString());
                    msg = "Es salto y tiene una distancia diferente de dos";
                } else if (!checkerType.isOpponent(auxPos[(iniPos.i + finalPos.i) / 2][(iniPos.j + finalPos.j) / 2])) {
                    isValid = false;
                    msg = "La posicion media no es oponente";
                } else if (auxPos[finalPos.i][finalPos.j] != Checker.EMPTY) {
                    isValid = false;
                    msg = "La posicion final no esta vacia";
                } else if (checkerType == Checker.BLACK) {
                    if (finalPos.i >= iniPos.i) {
                        isValid = false;
                        msg = "Es negra y no avanza en el sentido correcto";
                    }
                } else if (checkerType == Checker.RED) {
                    if (finalPos.i <= iniPos.i) {
                        isValid = false;
                        msg = "Es roja y no avanza en el sentido correcto";
                    }
                } else {
                    auxPos[iniPos.i][iniPos.j] = Checker.EMPTY;
                    auxPos[(iniPos.i + finalPos.i) / 2][(iniPos.j + finalPos.j) / 2] = Checker.EMPTY;
                    auxPos[finalPos.i][finalPos.j] = checkerType;

                }
                iniPos = arrPos[i];
            }
        }
        return isValid ? null : new MovementNotAllowedException(msg + " " + mv.toString());
    }

    private boolean isInvalidMov(Movement mv) {
        Position iniPos = mv.getListOfPositions().getFirst();
        Checker checkerType = positions[iniPos.i][iniPos.j];
        Player simulPlayer = new Player();
        boolean isBlack = checkerType.equals(Checker.BLACK) || checkerType.equals(Checker.BLACKKING);
        LinkedList<Movement> movsList = simulPlayer.calculatePossibleMoves(this.clone(), isBlack);
        return !movsList.contains(mv);
    }

    private void playerHasWon(boolean isBlack){
        Player simulPlayer = new Player();
        LinkedList<Movement> movsList = simulPlayer.calculatePossibleMoves(this.clone(), isBlack);
        if (isBlack) {
            if (redCheckers == 0 || movsList.isEmpty()) {
                setGameWonByPlayer(Checker.BLACK);
            }
        } else {
            if (blackCheckers == 0 || movsList.isEmpty()) {
                setGameWonByPlayer(Checker.RED);
            }
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();

        for (int i = -1; i < positions.length; i++) {
            if (i != -1) {
                sb.append(i + "  ");
            } else {
                sb.append("   ");
            }
            for (int j = 0; j < positions[0].length; j++) {
                if (i == -1) {
                    sb.append(j + " ");
                } else {
                    sb.append(positions[i][j].toString() + " ");
                }
            }
            sb.append("\n");
        }
        sb.append("Last movement: " + lastMove + "\n");
        return sb.toString();
    }

    @Override
    public Board clone() {
        Checker[][] pos = new Checker[8][8];
        for (int i = 0; i < pos.length; i++) {
            for (int j = 0; j < pos.length; j++) {
                pos[i][j] = positions[i][j];
            }
        }
        Board newBoard = new Board(pos);
        return newBoard;
    }

    private int checkersDiff() {
        int normalCheckers = ((getBlackCheckers() - getBlackKings()) - (getRedCheckers() - getRedKings())) * 3;
        int kings = (getBlackKings() - getRedKings()) * 5;
        return normalCheckers + kings;
    }

    private int closeToBeingKing() {
        int sum = 0;
        for (int i = 0; i < positions.length; i++) {
            for (int j = 0; j < positions[i].length; j++) {
                if (positions[i][j] == Checker.BLACK) {
                    sum = sum + 7 - i;
                } else if (positions[i][j] == Checker.RED) {
                    sum = sum - i;
                }
            }

        }
        return sum;
    }

    private int myCheckersProportion() {
        int proportion = (getBlackCheckers() - getRedCheckers()) / (getBlackCheckers() + getRedCheckers());
        return proportion;

    }

    private void calculateHeuristic(boolean color) {
        int rand = rnd.nextInt(10);
        int delta = 1;
        if (color == Player.RED) {
            delta = -1;
        }
        int oldHeuristic = heuristic;
        heuristic = delta * ((checkersDiff() * 10000000) + (closeToBeingKing() * 100000) + (myCheckersProportion() * 1000) + rand);
        firePropertyChange("heuristic", oldHeuristic, heuristic);
    }

    public static void main(String[] args) {
        Board tab = new Board();
        //Board tab =new Board(FileReaderUtil.getBoardFromFile("board.txt"));
        System.out.println(tab.toString());
        System.out.println(tab.blackCheckers);
        System.out.println(tab.blackKings);
        System.out.println(tab.redCheckers);
        System.out.println(tab.redKings);
        System.out.println(tab);
        System.out.println("================");
        System.out.println(tab.getHeuristic(Player.BLACK));
    }

    @Override
    public int compareTo(Board other) {
        return heuristic - other.heuristic;
    }
}
